#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <netdb.h>

#include <errno.h>
#include <cstdlib>
#include <cerrno>

#include <unistd.h>
#include <dirent.h>
#include <sys/socket.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/tcp.h>
#include <string>
#include <fstream>

#define DEFAULT_SERVER_PORT 9221
#define RCV_BUFFER_SIZE 10240
#define SEND_BUFFER_SIZE 10240
#define PATH_BUFFER_SIZE 10240
#define BLUE "\033[0;34m"
#define YELLOW "\033[1;33m"
#define GREEN "\033[0;32m"
#define L_BLUE "\033[1;34m"
#define RED "\033[38;5;1m"

using namespace std;

int init(int argc, char *argv[]);

char *receive_reply(int socket_fd);
void process_reply(char *reply);
void print_cwd(char *cwd);
void print_reply(char *reply);
string get_destination_path(char *, char *);
bool send_command_to_server();
void *download(void *arg);
void *upload(void *arg);

struct file_transmit_arg
{
    string file_name;
    struct sockaddr_in server_addr;
};

struct file_transmit_post_arg
{
    string file_name;
    int socket_fd;
};

struct sockaddr_in server_address;
bool finish = false;
char cwd[PATH_BUFFER_SIZE];
int control_socket_fd;
string input;
int main(int argc, char *argv[])
{
    cout << GREEN;
    control_socket_fd = init(argc, argv);
    if (control_socket_fd < 0)
        return 1;

    cout << "Connect to server success!\n";

    char *reply = receive_reply(control_socket_fd);
    if (reply == NULL)
    {
        close(control_socket_fd);
        return 1;
    }
    print_reply(reply);

    bool quit = false;
    char command[SEND_BUFFER_SIZE];
    while (true)
    {
        memset(command, 0, sizeof(command));
        getline(cin, input);
        bool has_space = false;
        int cmd_len = 0;

        for (int i = 0; i < input.size(); i++)
        {
            if (cmd_len == SEND_BUFFER_SIZE && i < input.size())
            {
                cout << "Command is too long!\n";
                return 1;
            }
            if (input[i] == ' ' || input[i] == '\t')
            {
                if (!has_space)
                {
                    command[cmd_len] = ' ';
                    cmd_len++;
                    has_space = true;
                }
            }
            else
            {
                command[cmd_len] = input[i];
                cmd_len++;
                has_space = false;
            }
        }

        char *cmd = strtok(command, " ");
        if (cmd == NULL)
        {
            print_cwd(cwd);
        }

        if (strcmp(cmd, "quit") == 0)
            break;

        if (strcmp(cmd, "ls") == 0)
        {
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "cd") == 0)
        {
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "mkdir") == 0)
        {
            char *name = strtok(NULL, " ");
            if (name == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "rmdir") == 0)
        {
            char *name = strtok(NULL, " ");
            if (name == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "touch") == 0)
        {
            char *name = strtok(NULL, " ");
            if (name == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "rm") == 0)
        {
            char *name = strtok(NULL, " ");
            if (name == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }
            if (!send_command_to_server())
                return 1;
        }

        else if (strcmp(cmd, "download") == 0)
        {

            char *src_c = strtok(NULL, " ");
            char *dest_c = strtok(NULL, " ");

            if (src_c == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }

            if (dest_c !=NULL && opendir(dest_c) == NULL)
            {
                cout << "path is not found\n";
                print_cwd(cwd);
                continue;
            }

            string src = src_c;
            string dest = get_destination_path(src_c, dest_c);

            string my_input = "download " + src;
            memset(cwd, 0, PATH_BUFFER_SIZE);
            write(control_socket_fd, my_input.c_str(), my_input.size());
            reply = receive_reply(control_socket_fd);

            // 无回应， 说明和服务器断开连接
            if (reply == NULL)
            {
                close(control_socket_fd);
                return 1;
            }

            process_reply(reply);
            if (strstr(reply, "Begin") != NULL)
            {
                struct file_transmit_arg arg;
                arg.file_name = dest;
                memcpy(&arg.server_addr, &server_address, sizeof(server_address));
                arg.server_addr.sin_port = htons(9222);

                pthread_t ntid;
                if (pthread_create(&ntid, NULL, download, (void *)&arg) != 0)
                {
                    cout << "Download file failed! Create thread error!\n";
                    finish = true;
                }

                while (!finish)
                    ;
                print_cwd(cwd);
                finish = false;
            }
        }
        else if (strcmp(cmd, "upload") == 0)
        {
            char *src_c = strtok(NULL, " ");
            char *dest_c = strtok(NULL, " ");
            if (src_c == NULL)
            {
                cout << "Bad command!\n";
                print_cwd(cwd);
                continue;
            }
            string src = src_c;
            string dest = get_destination_path(src_c, dest_c);
            string my_input = "upload " + src + " " + dest;
            write(control_socket_fd, my_input.c_str(), my_input.size());

            pthread_t ntid;
            file_transmit_post_arg arg;
            arg.file_name = src;
            arg.socket_fd = control_socket_fd;
            if (pthread_create(&ntid, NULL, upload, (void *)&arg) != 0)
            {
                cout << "Get file failed! Create thread error!\n";
                return 1;
            }

            reply = receive_reply(control_socket_fd);
            if (reply == NULL)
            {
                close(control_socket_fd);
                return 1;
            }
            else {
                print_reply(reply);
            }
                
        }
        else
        {
            write(control_socket_fd, input.c_str(), input.size());
            reply = receive_reply(control_socket_fd);
            if (reply == NULL)
            {
                close(control_socket_fd);
                return 1;
            }
            else
                print_reply(reply);
        }
    }

    close(control_socket_fd);
    return 0;
}

int init(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "Usage: Client <server address> <server port>\n";
        return -1;
    }

    int control_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (control_socket_fd < 0)
    {
        cout << "Create socket failed!\n";
        return -1;
    }

    struct timeval max_wait_time;
    max_wait_time.tv_sec = 0;
    max_wait_time.tv_usec = 30 * 1000;
    if (setsockopt(control_socket_fd, SOL_SOCKET, SO_RCVTIMEO, &max_wait_time, sizeof(max_wait_time)) != 0)
    {
        cout << "Set socket opt failed!\n";
        return -1;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;

    char server_ip[32];
    int input_ip_len = (strlen(argv[1]) < 32) ? strlen(argv[1]) : 31;
    strncpy(server_ip, argv[1], input_ip_len);
    server_ip[input_ip_len] = 0;
    // cout << "server ip " << server_ip << endl;
    if (inet_addr(server_ip) != INADDR_NONE)
        server_addr.sin_addr.s_addr = inet_addr(server_ip);
    else
    {
        struct hostent *server_host_name = gethostbyname(server_ip);
        if (server_host_name != 0)
            memcpy(&server_addr.sin_addr, &server_host_name->h_addr, sizeof(server_addr.sin_addr));
        else
        {
            cout << "Illegal server address!\n";
            return -1;
        }
    }

    int server_port = DEFAULT_SERVER_PORT;
    if (atol(argv[2]) != 0)
        server_port = atol(argv[2]);
    server_addr.sin_port = htons(server_port);

    memcpy(&server_address, &server_addr, sizeof(server_address));

    if (connect(control_socket_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        cout << "Connect to server failed!\n";
        return -1;
    }
    return control_socket_fd;
}

char rcv_buffer[RCV_BUFFER_SIZE];
char *receive_reply(int socket_fd)
{
    memset(rcv_buffer, 0, sizeof(rcv_buffer));
    int byte_rcv = read(socket_fd, rcv_buffer, sizeof(rcv_buffer));
    int errsv = errno;
    while (byte_rcv <= 0 && errsv == EAGAIN && strlen(rcv_buffer) == 0)
    {
        byte_rcv = read(socket_fd, rcv_buffer, sizeof(rcv_buffer));
        errsv = errno;
    }
    if (strlen(rcv_buffer) <= 0)
    {
        cout << "Receive message failed!\n";
        cout << "errno : " << errsv << endl;
        return NULL;
    }
    return rcv_buffer;
}

void print_cwd(char *cwd)
{
    cout << BLUE << cwd << GREEN;
    cout << "# ";
}

void *download(void *arg)
{
    struct file_transmit_arg *myArg = (struct file_transmit_arg *)arg;

    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0)
    {
        cout << "Create socket failed!\n";
        return NULL;
    }

    struct timeval max_wait_time;
    max_wait_time.tv_sec = 0;
    max_wait_time.tv_usec = 30 * 1000;
    if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &max_wait_time, sizeof(max_wait_time)) != 0)
    {
        cout << "Set socket opt failed!\n";
        return NULL;
    }
    if (connect(socket_fd, (struct sockaddr *)&myArg->server_addr, sizeof(struct sockaddr)) < 0)
    {
        cout << "Connect to server failed!\n";
        return NULL;
    }

    cout << "Connect to server at 9222 success\n";

    ofstream fout;
    cout << "file name " << myArg->file_name << endl;
    fout.open(myArg->file_name, ios_base::out | ios_base::binary);
    int errsv = errno;
    if (!fout)
    {
        cout << "Create file failed\n";
        cout << errsv << endl;
        return NULL;
    }
    char buffer[RCV_BUFFER_SIZE];
    memset(buffer, 0, RCV_BUFFER_SIZE);
    int bytes = read(socket_fd, buffer, RCV_BUFFER_SIZE);
    cout << "receive " << bytes << " bytes\n";
    errsv = errno;
    while (bytes > 0)
    {
        fout << buffer;
        memset(buffer, 0, RCV_BUFFER_SIZE);
        bytes = read(socket_fd, buffer, RCV_BUFFER_SIZE);
        cout << "receive " << bytes << " bytes\n";
    }
    close(socket_fd);
    fout.close();
    cout << "Receive " << myArg->file_name << " success!\n";
    finish = true;
    return NULL;
}

void *upload(void *arg)
{
    struct sockaddr_in data_sock_addr;
    memset(&data_sock_addr, 0, sizeof(data_sock_addr));
    data_sock_addr.sin_family = AF_INET;                // protocol family
    data_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); // set control socket address as local address
    data_sock_addr.sin_port = htons(8080);              // set control socket port
    socklen_t length = sizeof(data_sock_addr);

    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    int control_socket_opt = 1;
    setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &control_socket_opt, sizeof(control_socket_opt));
    if (listen_socket < 0)
    {
        cout << "Create socket failed!\n";
        return NULL;
    }

    if (bind(listen_socket, (struct sockaddr *)&data_sock_addr, length) < 0)
    {
        cout << "Bind socket failed!\n";
        return NULL;
    }

    if (getsockname(listen_socket, (struct sockaddr *)&data_sock_addr, &length) < 0)
    {
        cout << "Get socket name failed!\n";
        return NULL;
    }

    file_transmit_post_arg myArg = *((file_transmit_post_arg *)arg);
    int control_socket_fd = myArg.socket_fd;

    write(control_socket_fd, &data_sock_addr, sizeof(data_sock_addr));

    // cout << "Create socket at 9222 success!\n";

    listen(listen_socket, 1);

    int file_transmit_socket;
    // 尝试连接3次
    for (int i = 0; i < 3; i++)
    {
        file_transmit_socket = accept(listen_socket, NULL, NULL);
        if (file_transmit_socket > 0)
            break;
    }
    if (file_transmit_socket <= 0)
    {
        cout << "connect Fail\n";
        return NULL;
    }

    int src_file = open(myArg.file_name.c_str(), O_RDONLY, S_IREAD);
    if (src_file >= 0)
    {
        char buffer[RCV_BUFFER_SIZE];
        memset(buffer, 0, RCV_BUFFER_SIZE);
        int bytes = read(src_file, buffer, RCV_BUFFER_SIZE);
        while (0 < bytes)
        {
            write(file_transmit_socket, buffer, bytes);
            cout << "Send " << bytes << " bytes\n";
            memset(buffer, 0, RCV_BUFFER_SIZE);
            bytes = read(src_file, buffer, RCV_BUFFER_SIZE);
        }
        cout << "Send finish\n";
        close(src_file);
        close(file_transmit_socket);
    }
    else
        cout << "Open file fail\n";

    cout << "Receive " << myArg.file_name << " success!\n";
    return NULL;
}

void process_reply(char *reply)
{
    // memset(cwd, 0, PATH_BUFFER_SIZE);

    int reply_len = strlen(reply);
    // get cwd
    for (int i = 0; i < reply_len; i++)
    {
        if (reply[i] == '\n')
            break;
        else
            cwd[i] = reply[i];
    }
    // get message
    int cwd_len = strlen(cwd);
    for (int i = cwd_len + 1; i < reply_len; i++)
        reply[i - cwd_len - 1] = reply[i];
    reply[reply_len - cwd_len - 1] = '\0';
}

void print_reply(char *reply)
{
    process_reply(reply);
    if (strlen(reply) != 0)
        cout << reply << endl;
    print_cwd(cwd);
}

string get_destination_path(char *src_c, char *dest_c)
{
    // 提取文件名字
    string src = src_c;
    string::size_type iPos = src.find_last_of('/') + 1;
    string filename = src.substr(iPos, src.length() - iPos);

    if (dest_c == NULL)
    {
        return "./" + filename;
    }

    string dest = dest_c;
    if (dest[dest.length() - 1] != '/')
        dest = dest + "/";

    dest = dest + filename;
    return dest;
}

bool send_command_to_server()
{
    write(control_socket_fd, input.c_str(), input.size());
    char *reply = receive_reply(control_socket_fd);
    if (reply == NULL)
    {
        close(control_socket_fd);
        return false;
    }
    print_reply(reply);
    return true;
}